// @Author EthanScriptOn
// @Desc
package tool

import (
	"errors"
	"fmt"
	"gitee.com/fatzeng/srf_switch_basic_components/custom_exp"
	"reflect"
)

// FunctionKind Define two types of functions
type FunctionKind string

const (
	FreeFunction  FunctionKind = "FreeFunction"  // FreeFunction Anonymous functions
	BoundFunction FunctionKind = "BoundFunction" // BoundFunction Non-anonymous functions (struct functions)
)

// MethodCall Wrapper function calls
type MethodCall struct {
	instance     reflect.Value
	methodBody   reflect.Value
	methodType   reflect.Type
	result       []interface{}
	resultType   map[int]reflect.Type
	params       []interface{}
	paramType    map[int]reflect.Type
	err          *ErrorCollection
	functionKind FunctionKind
}

// GenerateFreeMethodCall Wrappers that generate functions (no receivers)
func GenerateFreeMethodCall(methodBody reflect.Value, params ...interface{}) (methodCallInstance *MethodCall, err error) {
	call := generateMethodCall(FreeFunction, methodBody, reflect.Value{}, params...)
	if err = call.methodBodyIsValid(); err != nil {
		return nil, err
	}
	call.methodType = methodBody.Type()
	return call, nil
}

// GenerateBoundMethodCall Wrapper for the generation method (with receiver)
func GenerateBoundMethodCall(methodBody reflect.Value, instance reflect.Value, params ...interface{}) (methodCallInstance *MethodCall, err error) {
	call := generateMethodCall(BoundFunction, methodBody, instance, params...)
	if err = call.instanceIsValid(); err != nil {
		return nil, err
	}
	if err = call.methodBodyIsValid(); err != nil {
		return nil, err
	}
	call.methodType = methodBody.Type()
	return call, nil
}

func generateMethodCall(functionKind FunctionKind, methodBody reflect.Value, instance reflect.Value, params ...interface{}) *MethodCall {
	return &MethodCall{
		instance:     instance,
		methodBody:   methodBody,
		params:       params,
		result:       make([]interface{}, 0),
		resultType:   make(map[int]reflect.Type),
		paramType:    make(map[int]reflect.Type),
		err:          new(ErrorCollection),
		functionKind: functionKind,
	}
}

func (m *MethodCall) methodBodyIsValid() (err error) {
	if m.methodBody.IsNil() {
		return errors.New("method verification failed, methodBody is nil ")
	}
	if !m.methodBody.IsValid() {
		return errors.New("method verification failed, methodBody is not valid ")
	}
	if m.methodBody.Kind() != reflect.Func {
		return errors.New("method verification failed, methodBody is not a function")
	}
	return
}

func (m *MethodCall) instanceIsValid() (err error) {
	if !m.instance.IsValid() {
		return errors.New("instance verification failed")
	}
	return
}

// GetParams Get the parameters
func (m *MethodCall) GetParams() []interface{} {
	return m.params
}

// SetParams Set the parameters
func (m *MethodCall) SetParams(params []interface{}) {
	m.params = params
}

// AppendParams Append parameters
func (m *MethodCall) AppendParams(params []interface{}) {
	m.params = append(m.params, params...)
}

// outputVerification Output parameter validation
func (m *MethodCall) outputVerification() *MethodCall {
	var err error
	defer m.errorRecord(&err)
	defer HandleErr(&err)
	fillOutType := func() {
		outLen := m.methodType.NumOut()
		for index := 0; index < outLen; index++ {
			m.resultType[index] = m.methodType.Out(index)
		}
	}
	switch m.functionKind {
	case BoundFunction, FreeFunction:
		fillOutType()
	}
	if err = m.parameterVerification(m.resultType, m.result); err != nil {
		err = fmt.Errorf("initialization failed mismatched output parameter types err: [%v]", err)
	}
	return m
}

// inputVerification Validation of the input parameters
func (m *MethodCall) inputVerification() *MethodCall {
	var err error
	defer m.errorRecord(&err)
	defer HandleErr(&err)
	fillParamType := func(index int) {
		start := 0
		paramLen := m.methodType.NumIn()
		for ; index < paramLen; index++ {
			m.paramType[start] = m.methodType.In(index)
			start++
		}
	}
	switch m.functionKind {
	// The first subscale of the parameter with the receiver is not checked
	case BoundFunction:
		fillParamType(1)
	case FreeFunction:
		fillParamType(0)
	}
	if err = m.parameterVerification(m.paramType, m.params); err != nil {
		err = fmt.Errorf("initialization failed mismatched input parameter type mismatch err: [%v]", err)
	}
	return m
}

// parameterVerification Parameter validation
func (m *MethodCall) parameterVerification(argumentsType map[int]reflect.Type, arguments []interface{}) (err error) {
	if len(arguments) != len(argumentsType) {
		return fmt.Errorf("the number of formal parameter lists does not match the number of parameter lists, parameter list len is [%v] formal parameter lists len is [%v]", len(arguments), len(argumentsType))
	}
	for index, argument := range arguments {
		typ, ok := argumentsType[index]
		if !ok {
			return fmt.Errorf("unable to find formal parameter with index position [%v]", index)
		}
		if typ.Kind() == reflect.Interface {
			continue
		}
		argumentType := reflect.TypeOf(argument)
		if typ != argumentType {
			return fmt.Errorf("the formal parameter type is [%v], and the actual parameter type is [%v]", typ, argumentType)
		}
	}
	return
}

// Call Call the function
func (m *MethodCall) Call() {
	var err error
	defer m.errorRecord(&err)
	defer HandleErrWithTemplate(&err, "an exception occurred during the call process")
	// Parameter validation before calling
	if err = m.inputVerification().GetError(); err != nil {
		return
	}
	// Function execution
	if err = m.authenticCall().GetError(); err != nil {
		return
	}
	// Parameter validation after call
	if err = m.outputVerification().GetError(); err != nil {
		return
	}
}

// authenticCall Function calls
func (m *MethodCall) authenticCall() *MethodCall {
	var err error
	defer m.errorRecord(&err)
	defer HandleErr(&err)
	args := make([]reflect.Value, 0)
	// Not an anonymous function, the receiver needs to be added in the first position of the parameter
	if m.functionKind == BoundFunction {
		args = append(args, m.instance)
	}
	// Build additional parameters
	for _, param := range m.params {
		args = append(args, reflect.ValueOf(param))
	}
	fnValue := m.methodBody
	callRes := fnValue.Call(args)
	for i := 0; i < m.methodType.NumOut(); i++ {
		var res interface{}
		val := callRes[i]
		if val.Kind() == reflect.Ptr {
			res = val.Elem().Interface()
		} else {
			res = val.Interface()
		}
		if err = m.setResultByIndex(res, i); err != nil {
			return m
		}
	}
	return m
}

// errorRecord Error collection
func (m *MethodCall) errorRecord(err *error) {
	if err != nil && *err != nil {
		m.err.WithErrorCollector(*err)
	}
}

// GetResultByIndex Get results based on subscripts
func (m *MethodCall) GetResultByIndex(index int) (interface{}, bool) {
	resultLength := len(m.result)
	if resultLength == 0 || index < 0 || index > resultLength-1 {
		return nil, false
	}
	return m.result[index], true
}

// GetResultByType Get results based on type
func (m *MethodCall) GetResultByType(typ reflect.Type) (interface{}, bool) {
	for _, res := range m.result {
		if reflect.TypeOf(res) == typ {
			return res, true
		}
	}
	return nil, false
}

// GetResultWithoutError Get the results, but ignore the exceptions in the results
func (m *MethodCall) GetResultWithoutError() ([]interface{}, bool) {
	resSlice := make([]interface{}, 0)
	errElem := reflect.TypeOf((*error)(nil)).Elem()
	for _, res := range m.result {
		if reflect.ValueOf(res).Type().Implements(errElem) {
			continue
		}
		resSlice = append(resSlice, res)
	}
	return resSlice, len(resSlice) != 0
}

// GetResult Get the results
func (m *MethodCall) GetResult() ([]interface{}, bool) {
	return m.result, len(m.result) != 0
}

// GetError MethodCall Errors in the execution process
func (m *MethodCall) GetError() error {
	if m.err == nil || m.err.GetError() == nil {
		return nil
	}
	return m.err.GetError()
}

// GetResultsError Obtain the result of type error in the result set
func (m *MethodCall) GetResultsError() error {
	err := new(custom_exp.MultiError)
	for _, res := range m.result {
		switch res.(type) {
		case error:
			err.AddError(res.(error))
		}
	}
	if len(err.GetErrors()) != 0 {
		return err
	}
	return nil
}

// setResultByIndex Add the result according to the subscript
func (m *MethodCall) setResultByIndex(res interface{}, index int) (err error) {
	defer m.errorRecord(&err)
	defer HandleErr(&err)
	if len(m.result)-1 < index {
		newLen := index + 1
		newResultSlice := make([]interface{}, newLen)
		copy(newResultSlice, m.result)
		m.result = newResultSlice
	}
	m.result[index] = res
	return
}

// setResultByIndex Set the result
func (m *MethodCall) setResult(res interface{}) {
	m.result = append(m.result, res)
}

// StructFunctions The stored structure of the method in the struct
type StructFunctions struct {
	ValueReceiver   map[string]reflect.Method // ValueReceiver   值接收者类型的方法
	PointerReceiver map[string]reflect.Method // PointerReceiver 指针接收者类型的方法
	CurrentTyp      reflect.Type
}

func GenerateStructFunctions(currentTyp reflect.Type) (instance *StructFunctions, err error) {
	if currentTyp == nil {
		return nil, errors.New("currentTyp is nil")
	}
	return &StructFunctions{ValueReceiver: make(map[string]reflect.Method), PointerReceiver: make(map[string]reflect.Method), CurrentTyp: currentTyp}, nil
}

// FoundMethod Find a method based on its name
func (s *StructFunctions) FoundMethod(methodName string) (instance reflect.Method, err error) {
	if methodName == "" {
		return instance, errors.New("methodName is empty")
	}
	instance, ok := s.PointerReceiver[methodName]
	if ok {
		return
	}
	instance, ok = s.ValueReceiver[methodName]
	if ok {
		return
	}
	return instance, fmt.Errorf("methodName: %s not found", methodName)
}

// SeparateStructFunctions A function that extracts the target instance
func (s *StructFunctions) SeparateStructFunctions() (err error) {
	typTemp := s.CurrentTyp
	return s.separateStructFunctions(typTemp)
}

// separateStructFunctions Cutting, cutting according to the type of method
func (s *StructFunctions) separateStructFunctions(typ reflect.Type) (err error) {
	if typ.Kind() != reflect.Struct && typ.Kind() != reflect.Ptr {
		return errors.New("not supported for types other than struct, ptr")
	}
	// Value receiver type function extraction
	isEmpty := true
	if typ.Kind() == reflect.Struct {
		for i := 0; i < typ.NumMethod(); i++ {
			method := typ.Method(i)
			s.ValueReceiver[method.Name] = method
		}
		isEmpty = false
		typ = reflect.PtrTo(typ)
	}
	// Pointer receiver type function extraction
	if typ.Kind() == reflect.Ptr {
		if len(s.ValueReceiver) <= 0 && isEmpty {
			typ = typ.Elem()
			return s.separateStructFunctions(typ)
		}
		for i := 0; i < typ.NumMethod(); i++ {
			method := typ.Method(i)
			methodName := method.Name
			if _, ok := s.ValueReceiver[methodName]; !isEmpty && ok {
				continue
			}
			s.PointerReceiver[method.Name] = method
		}
	}
	return
}

// MethodDispatcher Splitting the pointer receiver function from the value receiver function
type MethodDispatcher struct {
	sFunctions     *StructFunctions
	methodCall     map[string]*MethodCall
	targetInstance interface{}
}

// GetTarget Obtain the target instance
func (m *MethodDispatcher) GetTarget() interface{} {
	return m.targetInstance
}

// GetMethodCall Obtain the *MethodCall object based on the function name
func (m *MethodDispatcher) GetMethodCall(methodName string) (methodCall *MethodCall, err error) {
	if methodName == "" {
		return nil, errors.New("methodName is empty")
	}
	call, ok := m.methodCall[methodName]
	if !ok {
		return nil, fmt.Errorf("methodName: %s not found", methodName)
	}
	return call, nil
}

// Call Encapsulated calls *MethodCall
func (m *MethodDispatcher) Call(methodName string, params ...interface{}) (methodCall *MethodCall, err error) {
	defer HandleErrWithTemplate(&err, "an exception occurred while executing call")
	instance, err := m.sFunctions.FoundMethod(methodName)
	if err != nil {
		return
	}
	// Build a pointer receiver function
	callInstance, err := GenerateBoundMethodCall(instance.Func, reflect.ValueOf(m.targetInstance), params...)
	if err != nil {
		return
	}
	m.methodCall[methodName] = callInstance
	callInstance.Call()
	return callInstance, callInstance.GetError()
}

// GetStructFunctions obtain StructFunctions
func (m *MethodDispatcher) GetStructFunctions() *StructFunctions {
	return m.sFunctions
}

// GenerateMethodDispatcher The functions in the instance are extracted once based on the type of the target instance
func GenerateMethodDispatcher(targetInstance interface{}) (instance *MethodDispatcher, err error) {
	if targetInstance == nil {
		return nil, errors.New("targetInstance is nil")
	}
	targetTyp := reflect.TypeOf(targetInstance)
	sFunctionsInstance, err := GenerateStructFunctions(targetTyp)
	if err != nil {
		return nil, err
	}
	err = sFunctionsInstance.SeparateStructFunctions()
	if err != nil {
		return nil, err
	}
	return &MethodDispatcher{sFunctions: sFunctionsInstance, methodCall: make(map[string]*MethodCall), targetInstance: targetInstance}, nil
}
